/*
 *  Copyright 2008-2010 www.jrails.org
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.rails.core.model;

import java.sql.SQLException;
import java.sql.Timestamp;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.rails.core.db.SQLHelper;
import org.rails.core.model.attribute.Attribute;
import org.rails.core.model.attribute.AttributeException;
import org.rails.core.sql.Relation;
import org.rails.core.sql.SQLManager;
import org.rails.core.util.Utils;

/**
 * 实现数据库访问耦合
 * 
 * @author jack
 * 
 */
@SuppressWarnings("serial")
public abstract class ActiveRecord extends ActiveRecordBase {

	public ActiveRecord(DataSource dataSource) {
		super(dataSource);
	}

	public ActiveRecord(DataSource dataSource, Object id)
			throws RecordNotFoundException, SQLException {
		super(dataSource);
		setId(id);
		reFlush();
	}

	public ActiveRecord(DataSource dataSource, Map<String, Object> m) {
		super(dataSource, m);
	}

	public ActiveRecord(DataSource dataSource, String tableName) {
		super(dataSource, tableName);
	}

	@SuppressWarnings("unchecked")
	public void clearOtherAttribute() {
		final Map<String, Object> m = (Map<String, Object>) this.clone();
		final Iterator<String> it = m.keySet().iterator();
		while (it.hasNext()) {
			String key = it.next();
			if (!attributes.contains(key)) {
				this.remove(key);
			}
		}
	}

	public void validateAttribute(List<String> currAtts,
			List<String> notCurrAtts) throws AttributeException, SQLException {
		final ActiveRecord cloneRecord = (ActiveRecord) this.clone();
		for (Iterator<String> iterator = currAtts.iterator(); iterator
				.hasNext();) {
			final String key = iterator.next();
			final Attribute att = new Attribute(cloneRecord, key);
			att.error();
			put(att.getName(), att.getValue());
		}
		if (notCurrAtts != null) {
			for (Iterator<String> iterator = notCurrAtts.iterator(); iterator
					.hasNext();) {
				String key = iterator.next();
				Attribute att = new Attribute(cloneRecord, key);
				if (att.isConfig() && att.getDefaultValue() != null) {
					att.error();
					put(att.getName(), att.getValue());
				}
			}
		}
	}

	public <T extends ActiveRecord> T belongsTo(T target,
			Map<String, Object> params) throws SQLException {
		return belongsTo(target, target.getClass().getSimpleName(), params);
	}

	public <T extends ActiveRecord> T belongsTo(T target) throws SQLException {
		return belongsTo(target, target.getClass().getSimpleName(),
				new HashMap<String, Object>());
	}

	public <T extends ActiveRecord> T belongsTo(T target, String asName)
			throws SQLException {
		return belongsTo(target, asName, new HashMap<String, Object>());
	}

	public <T extends ActiveRecord> T belongsTo(T target, final String asName,
			Map<String, Object> params) throws SQLException {
		final Relation relation = new Relation(this, target, asName, params);
		final List<Map<String, Object>> ls = target.findBySqlManager(relation
				.getBelongsToSqlManager());
		if (ls.size() == 0) {
			return null;
		} else {
			target.putAll(ls.get(0));
			return (T) target;
		}
	}

	public <T extends ActiveRecord> T hasOne(T target,
			Map<String, Object> params) throws SQLException {
		return hasOne(target, target.getClass().getSimpleName(), params);
	}

	public <T extends ActiveRecord> T hasOne(T target) throws SQLException {
		return hasOne(target, target.getClass().getSimpleName(),
				new HashMap<String, Object>());
	}

	public <T extends ActiveRecord> T hasOne(T target, String asName)
			throws SQLException {
		return hasOne(target, asName, new HashMap<String, Object>());
	}

	public <T extends ActiveRecord> T hasOne(T target, String asName,
			Map<String, Object> params) throws SQLException {
		final Relation relation = new Relation(this, target, asName, params);
		final List<Map<String, Object>> ls = target.findBySqlManager(relation
				.getHasOneSqlManager());
		if (ls.size() == 0) {
			return null;
		} else {
			target.putAll(ls.get(0));
			return target;
		}
	}

	public <T extends ActiveRecord> List<T> hasMany(T target)
			throws SQLException {
		return hasMany(target, target.getClass().getSimpleName(),
				new HashMap<String, Object>());
	}

	public <T extends ActiveRecord> List<T> hasMany(T target,
			Map<String, Object> params) throws SQLException {
		return hasMany(target, target.getClass().getSimpleName(), params);
	}

	public <T extends ActiveRecord> List<T> hasMany(T target, String asName)
			throws SQLException {
		return hasMany(target, asName, new HashMap<String, Object>());
	}

	@SuppressWarnings("unchecked")
	public <T extends ActiveRecord> List<T> hasMany(T target, String flag,
			Map<String, Object> params) throws SQLException {
		final Relation relation = new Relation(this, target, params);
		final List<T> many = new ArrayList<T>();
		final List<Map<String, Object>> list = target.findBySqlManager(relation
				.getHasManySqlManager());
		for (Iterator<Map<String, Object>> iterator = list.iterator(); iterator
				.hasNext();) {
			final Map<String, Object> map = iterator.next();
			final ActiveRecord record = (ActiveRecord) target.clone();
			record.clear();
			record.putAll(map);
			many.add((T) record);
		}
		return many;
	}

	public <T extends ActiveRecord> List<T> marries(T target)
			throws SQLException {
		return marries(target, target.getClass().getSimpleName(),
				new HashMap<String, Object>());
	}

	public <T extends ActiveRecord> List<T> marries(T target,
			Map<String, Object> params) throws SQLException {
		return marries(target, target.getClass().getSimpleName(), params);
	}

	public <T extends ActiveRecord> List<T> marries(T target,
			final String asName) throws SQLException {
		return marries(target, asName, new HashMap<String, Object>());
	}

	@SuppressWarnings("unchecked")
	public <T extends ActiveRecord> List<T> marries(T target, String asName,
			Map<String, Object> params) throws SQLException {
		final Relation relation = new Relation(this, target, asName, params);
		final List<T> many = new ArrayList<T>();
		final List<Map<String, Object>> list = target.findBySqlManager(relation
				.getMarriesSqlManager());
		for (Iterator<Map<String, Object>> iterator = list.iterator(); iterator
				.hasNext();) {
			final Map<String, Object> map = (Map<String, Object>) iterator
					.next();
			final ActiveRecord record = (ActiveRecord) target.clone();
			record.clear();
			record.putAll(map);
			many.add((T) record);
		}
		return many;
	}

	@SuppressWarnings("unchecked")
	public void errors() throws RecordException {
		final List<String> keys = Utils.getKeys(this);
		final List<Attribute> errorAtts = new ArrayList<Attribute>();
		Attribute att = null;
		boolean te = false;
		StringBuffer message = new StringBuffer();
		for (Iterator<String> iterator = keys.iterator(); iterator.hasNext();) {
			final String key = iterator.next();
			att = new Attribute((ActiveRecord) this.clone(), key);
			try {
				att.error();
				this.put(key, att.getValue());
			} catch (AttributeException e) {
				te = true;
				message.append(e.getMessage()).append("|");
				att.setExceMsg(e.getMessage());
				errorAtts.add(att);
			}
		}
		if (te)
			throw new RecordException(errorAtts, message.toString());
	}

	@SuppressWarnings("unchecked")
	public boolean create() throws AttributeException, SQLException {
		final Timestamp curr = new Timestamp(new Date().getTime());
		put("created_at", curr);
		put("updated_at", curr);
		clearOtherAttribute();

		final List<String> atts = getAttributes();
		atts.remove(getTable().getPrimaryKey());

		final List<String> currAtts = Utils.getKeys(this);
		currAtts.remove(getTable().getPrimaryKey());

		final List<String> notCurrAtts = new ArrayList<String>();
		for (String att : atts) {
			if (!currAtts.contains(att))
				notCurrAtts.add(att);
		}

		validateAttribute(currAtts, notCurrAtts);
		final SQLManager sqlManager = new SQLManager(this);
		return sqlHelper.executeCreate(sqlManager) > 0;
	}

	@SuppressWarnings("unchecked")
	public boolean update() throws AttributeException, SQLException {
		final Timestamp curr = new Timestamp(new Date().getTime());
		put("updated_at", curr);
		removeAttribute("created_at");
		clearOtherAttribute();
		validateAttribute(Utils.getKeys(this), null);
		final SQLManager sqlManager = new SQLManager(this);
		sqlManager.addCondition(Utils.format("{0} = :{1}",
				table.getFormatPrimaryKey(), table.getPrimaryKey()));
		return sqlHelper.executeUpdate(sqlManager) > 0;
	}

	public boolean delete() throws SQLException {
		final SQLManager sqlManager = new SQLManager(this);
		sqlManager.addCondition(Utils.format("{0} = :{1}",
				table.getFormatPrimaryKey(), table.getPrimaryKey()));
		sqlManager.addParameter(table.getPrimaryKey(), getId());
		return sqlHelper.executeDelete(sqlManager) > 0;
	}

	public static <T extends ActiveRecord> int delete(T record,
			String condition, Object... params) throws SQLException {
		final SQLHelper sqlHelper = new SQLHelper(record.getDataSource());
		final String sql = Utils.format("DELETE FROM {0}{1}{2}", record
				.getTable().getName(), Utils.isEmpty(condition) ? ""
				: " WHERE ", condition);
		return sqlHelper.executeUpdate(sql, params);
	}

	public List<Map<String, Object>> findBySqlManager(SQLManager sqlManager)
			throws SQLException {
		sqlHelper.setMaxResult(sqlManager.getMaxResult());
		return sqlHelper.executeQuery(sqlManager);
	}

	@SuppressWarnings("unchecked")
	public static <T extends ActiveRecord> List<T> findBySqlManager(T record,
			SQLManager sqlManager) throws SQLException {
		final List<T> res = new ArrayList<T>();
		final List<Map<String, Object>> list = record
				.findBySqlManager(sqlManager);
		for (Iterator<Map<String, Object>> iterator = list.iterator(); iterator
				.hasNext();) {
			final T t = (T) record.clone();
			t.putAll(iterator.next());
			res.add(t);
		}
		return res;
	}

	public List<Map<String, Object>> findBySql(String sql, List<Object> params)
			throws SQLException {
		return sqlHelper.executeQuery(sql, params);
	}

	public List<Map<String, Object>> findBySql(String sql, Object[] params)
			throws SQLException {
		return sqlHelper.executeQuery(sql, params);
	}

	public Map<String, Object> findFirst(SQLManager sqlManager)
			throws SQLException {
		sqlManager.setMaxResult(1);
		final List<Map<String, Object>> ls = findBySqlManager(sqlManager);
		if (ls == null || ls.size() == 0)
			return null;
		else
			return ls.get(0);
	}

	public static <T extends ActiveRecord> T findFirst(T record,
			SQLManager sqlManager) throws SQLException {
		sqlManager.setMaxResult(1);
		final Map<String, Object> m = record.findFirst(sqlManager);
		if (m != null) {
			record.putAll(m);
			return record;
		} else
			return null;
	}

	public static <T extends ActiveRecord> T parseQueryResult(T record,
			Map<String, Object> result) {
		for (Iterator<String> iterator = record.getAttributes().iterator(); iterator
				.hasNext();) {
			String att = iterator.next();
			String key = Utils.format("{0}_{1}", record.getTable().getName(),
					att);
			if (result.containsKey(key)) {
				record.put(att, result.get(key));
			}
		}
		return record;
	}

	public int executeUpdate(String sql, List<Object> params)
			throws SQLException {
		return sqlHelper.executeUpdate(sql, params);
	}

	public int executeUpdate(String sql, Object[] params) throws SQLException {
		return sqlHelper.executeUpdate(sql, params);
	}

	public void reFlush() throws RecordNotFoundException, SQLException {
		final String condKey = Utils.format("{0}.condition", getClass()
				.getSimpleName());
		final String joinKey = Utils.format("{0}.join", getClass()
				.getSimpleName());
		final String orderKey = Utils.format("{0}.order", getClass()
				.getSimpleName());
		final String havingKey = Utils.format("{0}.having", getClass()
				.getSimpleName());
		final String lastKey = Utils.format("{0}.last", getClass()
				.getSimpleName());
		final SQLManager sqlManager = new SQLManager(this);
		sqlManager.addCondition(Utils.format("{0}.{1} = :{2}",
				table.getFormatName(), table.getFormatPrimaryKey(),
				table.getPrimaryKey()));
		sqlManager.addColumn(Utils.format("{0}.*", table.getFormatName()));
		sqlManager.addCondition(config.get(condKey));
		sqlManager.addJoin(config.get(joinKey));
		sqlManager.addOrder(config.get(orderKey));
		sqlManager.addHaving(config.get(havingKey));
		sqlManager.addLast(config.get(lastKey));
		sqlManager.addParameter(table.getPrimaryKey(), getId());
		List<Map<String, Object>> result = sqlHelper.executeQuery(sqlManager);
		if (result.size() == 0)
			throw new RecordNotFoundException();
		else {
			this.clear();
			this.putAll(result.get(0));
		}
	}

	public boolean married(ActiveRecord target) throws SQLException {
		final Relation relation = new Relation(this, target);
		final Object[] params = new Object[] { getId(), target.getId() };
		return ((Long) sqlHelper.executeQuery(relation.getMarriedSql(), params)
				.get(0).get("c")) > 0;
	}

	public boolean marry(ActiveRecord target) throws SQLException {
		final Relation relation = new Relation(this, target);
		final Object[] params = new Object[] { getId(), target.getId() };
		return sqlHelper.executeUpdate(relation.getMarrySql(), params) > 0;
	}

	public boolean unMarry(ActiveRecord target) throws SQLException {
		final Relation relation = new Relation(this, (ActiveRecord) target);
		final Object[] params = new Object[] { getId(), target.getId() };
		return sqlHelper.executeUpdate(relation.getUnMarrySql(), params) > 0;
	}

	public static int[] createBatch(List<? extends ActiveRecord> records)
			throws SQLException {
		return records.get(0).getSqlHelper().executeInsertBatch(records);
	}

	public static int[] deleteBatch(List<? extends ActiveRecord> records)
			throws SQLException {
		return records.get(0).getSqlHelper().executeDeleteBatch(records);
	}

	public static int[] updateBatch(List<? extends ActiveRecord> records)
			throws SQLException {
		return records.get(0).getSqlHelper().executeUpdateBatch(records);
	}

}
